package com.gao.redlock.service.impl;

import com.gao.redlock.common.Constants;
import com.gao.redlock.common.id.IdGenerator;
import com.gao.redlock.common.req.OrderReq;
import com.gao.redlock.common.res.OrderResult;
import com.gao.redlock.common.res.ProcessResult;
import com.gao.redlock.common.res.StockResult;
import com.gao.redlock.mapper.ProductMapper;
import com.gao.redlock.po.Product;
import com.gao.redlock.service.OrderService;
import com.gao.redlock.util.RedisUtil;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * Redisson 分布式锁
 */
@Service("orderRedisson")
public class OrderRedissonImpl implements OrderService {

    private Logger logger = LoggerFactory.getLogger(OrderService.class);

    @Autowired
    RedisUtil redisUtil;

    @Resource
    private ProductMapper productMapper;

    @Resource
    private Map<Constants.Ids, IdGenerator> idGeneratorMap;

    @Autowired
    private RedissonClient redissonClient;

    @Override
    public ProcessResult doProcess(OrderReq orderReq) {
        // 订单创建
        OrderResult orderResult = createOrder(orderReq);
        if (!Constants.ResponseCode.SUCCESS.getCode().equals(orderResult.getCode())) {
            return new ProcessResult(orderResult.getCode(), orderResult.getInfo());
        }
        // 更新产品库存，应该异步去处理数据库库存，这里简单处理下
        productMapper.updateProductStock(orderResult);
        return new ProcessResult(Constants.ResponseCode.SUCCESS.getCode(), Constants.ResponseCode.SUCCESS.getInfo());
    }

    /**
     * 创建订单
     * @return
     */
    public OrderResult createOrder(OrderReq req) {
        // 1、查询产品库存信息
        Product productInfo = productMapper.queryProductById(req.getProductId());
        // 2、扣减库存，加锁
        StockResult stockResult = deductStockByRedisson(req.getUserId(), req.getProductId(), productInfo.getStockCount());
        if (!Constants.ResponseCode.SUCCESS.getCode().equals(stockResult.getCode())) {
            return new OrderResult(stockResult.getCode(), stockResult.getInfo());
        }
        // 3、生成订单号
        Long orderNo = idGeneratorMap.get(Constants.Ids.SnowFlake).nextId();
        OrderResult orderResult = new OrderResult(Constants.ResponseCode.SUCCESS.getCode(), Constants.ResponseCode.SUCCESS.getInfo());
        orderResult.setOrderNo(orderNo);
        orderResult.setProductId(productInfo.getProductId());
        orderResult.setStockSurplusCount(stockResult.getStockSurplusCount());

        return orderResult;
    }

    /**
     * Redisson 分布式锁 扣减库存
     * @param uId          用户ID
     * @param productId    产品ID
     * @param stockCount   库存数量
     * @return
     */
    public StockResult deductStockByRedisson(Long uId, Long productId, Integer stockCount) {
        // 1、获取产品的分布式库存 key
        String stockRedissonLock = Constants.RedisKey.getProductStockRedissonLock(productId);
        // 库存 key
        String stockKey = Constants.RedisKey.getProductStockCount(productId);
        // 给库存 key 加锁
        RLock rLock = redissonClient.getLock(stockRedissonLock);
        Integer stockUsedCount = 0;
        try {
            // 2、加锁，尝试获取锁，获取到立即返回true,未获取到立即返回false
            boolean isLock = rLock.tryLock(30, 10, TimeUnit.SECONDS);
            if (isLock) {
                // 3、扣减库存，返回当前库存已用数量
                stockUsedCount = (int) redisUtil.incr(stockKey, 1);
                logger.info("已用库存：{}",stockUsedCount);
                // 4. 如果超出库存，恢复原始库存，返回失败结果
                if (stockUsedCount > stockCount) {
                    // 恢复库存
                    redisUtil.decr(stockKey, 1);
                    logger.info("{}库存不足：", productId);
                    return new StockResult(Constants.ResponseCode.OUT_OF_STOCK.getCode(), Constants.ResponseCode.OUT_OF_STOCK.getInfo());
                }
            }
        } catch (Exception e) {
            logger.error("加锁失败！");
            e.printStackTrace();
        } finally {
            // 5、释放锁
            if (rLock.isHeldByCurrentThread()) {
                rLock.unlock();
                logger.info("分布式锁释放:{},ThreadId :{}", stockRedissonLock, Thread.currentThread().getId());
            }
        }

        return new StockResult(Constants.ResponseCode.SUCCESS.getCode(), Constants.ResponseCode.SUCCESS.getInfo(), stockKey, stockCount - stockUsedCount);
    }
}
